home *** CD-ROM | disk | FTP | other *** search
Java Source | 1998-11-06 | 11.2 KB | 281 lines |
- /*
- Engine class for support of currency field input.
-
- Created 5/4/98 by Paul Lancaster.
- */
-
- package com.symantec.itools.swing;
-
- import java.math.BigDecimal; // used for rounding
-
- public class CurrencyEngine {
- public CurrencyEngine() {} // default constructor
-
- public void setCommas (boolean b) { _commas = b ; }
- public void setCurrencyLeading (boolean b) { _currencySymbolLeads = b ; }
- public void setATMmode (boolean b) { _ATMmode = b ; }
- public void setCurrencySymbol (String s) { _currencySymbol = s ; }
- public void setDecimalPoint (char c) { _decimalPoint = c ; }
- public void setSeparator (char c) { _separator = c ; }
- public void setDigitsAfterDecimal(int c) { _digitsAfterDecimal = c ; }
- public boolean getCommas ( ) { return _commas ; }
- public boolean getCurrencyLeading ( ) { return _currencySymbolLeads ; }
- public boolean getATMmode ( ) { return _ATMmode ; }
- public String getCurrencySymbol ( ) { return _currencySymbol ; }
- public char getDecimalPoint ( ) { return _decimalPoint ; }
- public char getSeparator ( ) { return _separator ; }
- public int getDigitsAfterDecimal( ) { return _digitsAfterDecimal ; }
-
- /* Called to initialize the display of the masked data.
- The first parameter is the current data in the field.
- The second parameter holds the string that should be displayed.
- The return value is the initial caret position.
- */
- public int initDisplay(String data, StringBuffer newData) {
- if (data.length() == 0) { // no input data
- normalize(newData);
- } else { // data coming in
- newData.append(data);
- scale(newData);
- }
- return _ATMmode ? newData.length() : 0;
- }
-
- /* This is the main workhorse method.
- It's called for every key stroke corresponding to displayable
- characters once editing begins.
- The 1st parameter is the user keystroke event object.
- The 2nd parameter is the current cursor position (zero based).
- The 3rd parameter is the current text from the component.
- The 4th parameter is output and is what should be displayed in the component.
- The 5th and 6th parameters are the selection start and end, respectively.
- The return value is the new cursor position within the "newData"
- parameter (zero based), unless it is negative, in which case:
- -1 means the input is inconsistent.
- */
- public int processKey(java.awt.event.KeyEvent e, int pos, String data,
- StringBuffer newData, int selStart, int selEnd) {
- char key = e.getKeyChar();
- newData.append(data); // init output to input
- int decpos = data.indexOf(_decimalPoint);
- int datalen = data.length();
- int keyCode = e.getKeyCode();
- if (_ATMmode) {
- switch (keyCode) {
- case e.VK_LEFT:
- return pos - (pos == 0 ? pos : (pos == decpos + 1 ? 2 : 1));
- case e.VK_RIGHT:
- return pos + (pos == datalen ? 0 : (pos == decpos - 1 ? 2 : 1));
- case e.VK_BACK_SPACE:
- case e.VK_DELETE:
- if (pos == decpos) // don't allow decimal point deletion
- return -1;
- if (selStart < selEnd) {
- clearSelectedText(selStart, selEnd, newData, true);
- return selStart;
- }
- deleteChar(newData, pos == datalen ? pos - 1 : pos);
- normalize(newData);
- int delta = newData.length() - datalen;
- return pos == decpos - 1 ? (decpos + (0 == delta ? 1 : 0)) : pos + delta;
- } // end switch on navigation key
- } else { // not ATM mode
- // Have to handle backspace & delete in non-ATM mode to ensure
- // right justification occurs if component is awt.TextField
- switch (keyCode) {
- case e.VK_BACK_SPACE:
- if (selEnd == selStart && pos > 0) {
- clearSelectedText(pos - 1, pos, newData, false);
- return pos - 1;
- } else {
- clearSelectedText(selStart, selEnd, newData, false);
- return pos;
- }
- case e.VK_DELETE:
- if (selEnd == selStart && pos < datalen)
- clearSelectedText(pos, pos + 1, newData, false);
- else
- clearSelectedText(selStart, selEnd, newData, false);
- return pos;
- } // end switch on navigation key
- }
- if (!Character.isDigit(key)) { // input not a digit?
- if (key != _decimalPoint || _ATMmode)
- return -1; // must be decimal point if not digit
- if (decpos != -1)
- return pos <= decpos ? decpos + 1 : -1;
- }
- clearSelectedText(selStart, selEnd, newData, true);
- if (_ATMmode) {
- //if (selStart < selEnd && decpos < selStart) {
- if (selStart < selEnd ) {
- //this is a quick fix to avoid StringIndexOutOfBoundsExceptions
- //@todo:handle cases (properly) when a digit to the left of decimal
- // separator shifts to the right when selection is deleted
- newData.insert(selStart, key);
- }else{
- newData.insert(pos, key);
- }
- normalize(newData);
- return selStart < selEnd ? selStart : pos + newData.length() - datalen;
- } else{
- //newData.insert(pos, key);
- //this is a quick fix to avoid StringIndexOutOfBoundsExceptions
- if (selStart < selEnd ) {
- pos = selStart;
- newData.insert(selStart, key);
- }else{
- newData.insert(pos, key);
- }
- }
- return pos + 1;
- }
-
- // Return true iff the engine handles the given key stroke.
- public boolean isHandledKey(java.awt.event.KeyEvent e) {
- if (!Character.isISOControl(e.getKeyChar()))
- return true; // we handle all non-control characters
- switch (e.getKeyCode()) { // here are the controls we handle
- case e.VK_BACK_SPACE:
- case e.VK_DELETE:
- return true;
- case e.VK_RIGHT:
- case e.VK_LEFT:
- return _ATMmode && !e.isShiftDown();
- }
- return false; // don't handle control chars by default
- }
-
- public void postFormat(String data, StringBuffer newData) {
- newData.append(data);
- scale(newData);
- data = newData.toString();
- newData.setLength(0);
- if (_currencySymbolLeads)
- newData.append(_currencySymbol);
- int j = newData.length();
- newData.append(data);
- if (!_currencySymbolLeads)
- newData.append(_currencySymbol);
- int decpos = data.indexOf(_decimalPoint);
- for (int i = 0; i < decpos; i++, j++)
- if (_commas && i > 0 && i % 3 == decpos % 3)
- newData.insert(j++, _separator);
- }
-
- public void cut(StringBuffer newData, int selStart, int selEnd) {
- clearSelectedText(selStart, selEnd, newData, true);
- }
-
- public void paste(StringBuffer data, String pasteData, int pos, int selStart, int selEnd) {
- clearSelectedText(selStart, selEnd, data, false);
- String s = data.toString();
- int decpos = s.indexOf(_decimalPoint);
- data.setLength(0);
-
- // Sanitize the paste data to remove non-digits.
- StringBuffer pd = new StringBuffer();
- int pastelen = pasteData.length();
- for (int i = 0; i < pastelen; i++) {
- char c = pasteData.charAt(i);
- if (Character.isDigit(c) || (decpos == -1 && _decimalPoint == c))
- pd.append(c);
- }
-
- data.append(s.substring(0, pos) + pd.toString() + s.substring(pos, s.length()));
- if (_ATMmode)
- normalize(data);
- }
-
- void clearSelectedText(int selStart, int selEnd, StringBuffer data, boolean norm) {
- int selLen = selEnd - selStart;
- if (selLen > 0) { // only if selected text exists
- String s = data.toString();
- int oldlen = data.length();
- data.insert(0, s.substring(0, selStart) + s.substring(selEnd));
- data.setLength(oldlen - selLen);
- if (_ATMmode && norm)
- normalize(data);
- }
- }
-
- /* The input is a digit string (possibly with a decimal point) that's presumed
- to be the mantissa of a floating point value whose exponent is the number of digits
- minus the digitsAfterDecimal property. The incoming decimal point position is
- ignored unless it's found to be correct. It's adjusted to be to the left of
- exactly digitsAfterDecimal digits, with zero fill as required. Values less
- than unity are given a leading zero.
-
- Examples: (with _digitsAfterDecimal == 2)
-
- Input Output
- 25 0.25
- 1.375 13.75
- 295.4 29.54
- */
- private void normalize(StringBuffer sigfigs) {
- int decpos = sigfigs.toString().indexOf(_decimalPoint);
- int lod = sigfigs.toString().length() - 1 - _digitsAfterDecimal; // # digits left of dec. pt.
- if (decpos != lod || decpos < 1) { // if decimal point's absent or in wrong place
- if (decpos != -1) { // remove existing decimal point
- deleteChar(sigfigs, decpos);
- lod--; // since first LOD calculation assumed no dec. pt.
- }
- while (lod++ < 0) // zero fill to right of decimal if needed
- sigfigs.insert(0, '0');
- sigfigs.insert(sigfigs.length() - _digitsAfterDecimal, _decimalPoint);
- }
-
- // Trim down to at most one leadng zero
- while (sigfigs.charAt(0) == '0' && sigfigs.charAt(1) != _decimalPoint)
- deleteChar(sigfigs, 0);
- }
-
- /* Reformats the parameter to have the right number of digits after the
- decimal point. The position (or absence) of the decimal point on entry
- determines the exponent. If the input number of digits to the right
- of the decimal is less than _digitsAfterDecimal, zeros are appended.
- If there are more than _digitsAfterDecimal digits after the decimal,
- rounding is done.
-
- Examples: (with _digitsAfterDecimal == 2)
-
- Input Output
- 25 25.00
- 1.375 1.38
- 295.4 295.40
- 999.995 1000.00
- */
- private void scale(StringBuffer newData) {
- if (newData.length() == 0)
- newData.append('0');
- String data = newData.toString();
- if (_decimalPoint != '.') // BigDecimal needs "normal" decimal point
- data = data.replace(_decimalPoint, '.');
- data = (new BigDecimal(data)).setScale(_digitsAfterDecimal, BigDecimal.ROUND_HALF_UP).toString();
- if (_decimalPoint != '.')
- data = data.replace('.', _decimalPoint);
- newData.setLength(0);
- newData.append(data);
- }
-
- // Delete the character at the given position from the given StringBuffer
- void deleteChar(StringBuffer data, int pos) {
- int len = data.length();
- if (pos >= 0 && pos < len) {
- String s = data.toString();
- data.insert(0, s.substring(0, pos) + (pos < len -1 ? s.substring(pos + 1) : ""));
- data.setLength(len - 1);
- }
- }
-
- // Variables
- boolean _commas = true ; // true to display thousands separator
- boolean _currencySymbolLeads = true ; // true if currency symbol precedes value
- boolean _ATMmode = false; // true for right-to-left data entry
- String _currencySymbol = "$" ;
- char _decimalPoint = '.' ;
- char _separator = ',' ;
- int _digitsAfterDecimal = 2 ;
- }
-